import type { ElementHideConfig } from '../../../element/service/types'
import type { DynamicHideMetaOptions } from '../../../lib/dynamic_hide'
import type { InjectorType } from '../../../lib/style_injector'
import type { IElementCSSOption, ProcessedElementConfig } from '../../../types/constants'
import { ElementTag } from '../../../element/config/element_tag'
import { WebElement } from '../../../element/config/web_element'
import { checkElementHideSettings } from '../../../element/service'
import { ElementCSSHandler } from '../../../element/service/element_css_handler'
import {
  ElementHideCssOperation,
  ElementHideOperationType,
} from '../../../element/service/types'
import { BaseModule } from '../../../lib/abstract'
import { DynamicHide } from '../../../lib/dynamic_hide'
import { LogStyle } from '../../../lib/logger'
import { ToolKit } from '../../../lib/utils/toolkit'
import {
  ELEMENT_SAVE_CONFIG,
  ELEMENT_SAVE_SEPARATOR,
  ElementConfigSource,
  EVENT_EMITTER_NAMES,
  HOSTNAME,
  logger,
  SUPPORTED_PLATFORMS,
  VisibilitySettingsManager,
} from '../../../types/constants'
import { ConfigManager } from '../../config_manager'
import { AppModule } from '../../config_manager/types'

interface Config {
  enabled?: boolean
}

export class ElementHider extends BaseModule {
  // @Decorators.Identity
  readonly id: string = 'element_hider'
  readonly name: string = 'ElementHider'
  // @Decorators.PublicState
  isInitialized: boolean = false
  private readonly configManager: ConfigManager = ConfigManager.getInstance()
  private readonly elementTag: ElementTag = ElementTag.getInstance()
  private readonly webElement: WebElement = WebElement.getInstance()
  readonly supportedPlatforms: string[] = this.configManager.moduleSupportedPlatforms(this.name)
  private readonly eventId: string = EVENT_EMITTER_NAMES.hideElement
  // @Decorators.Dependency
  private static instance: ElementHider
  private readonly CSSHandler: ElementCSSHandler = ElementCSSHandler.getInstance()

  config: Config = {
    enabled: true,
  }

  constructor() {
    super()

    if (this.config.enabled && this.configManager.getFeatureEnabled(AppModule.ElementHider.id)) {
      this.registerEventListener()
    }
  }

  /**
   * 获取单例实例
   */
  public static getInstance(): ElementHider {
    if (!ElementHider.instance) {
      ElementHider.instance = new ElementHider()
    }
    return ElementHider.instance
  }

  handleCleanupEventListeners(): void {
    super.handleCleanupEventListeners()

    this.unregisterEventListener()
  }

  core(): void {
    this.loadConfig()

    if (!this.config.enabled)
      return

    // 首次隐藏
    this.hiddenElement()

    // 二次隐藏
    if (HOSTNAME === SUPPORTED_PLATFORMS.ZHI_HU_QUESTION) {
      ToolKit.async.onWindowLoad(() => {
        this.hiddenElement()
      })
    }
  }

  async onToggleCallback(_id: string, checked: boolean): Promise<void> {
    await logger.executeWithLogging(`${checked ? '激活' : '关闭'}元素隐藏`, () => {
      super.onToggleCallback(_id, checked)
    }, {
      collapsed: true,
      color: LogStyle.groupHeader,
    })
  }

  registerEventListener(): void {
    this.eventEmitter.on(this.eventId, () => {
      this.hiddenElement()
    })
  }

  unregisterEventListener(): void {
    this.eventEmitter.off(this.eventId)
  }

  getNotStorageSelectors(userSettings: Record<string, boolean>): Array<[string, boolean]> {
    const generateKey = (item: ProcessedElementConfig) => `${ELEMENT_SAVE_CONFIG.ELEMENT.id}${ELEMENT_SAVE_SEPARATOR}${item.nameEn}${ELEMENT_SAVE_SEPARATOR}${item.selector}`

    return this.webElement.getSelectors().filter((item) => {
      const shouldShow = item.show
      return !shouldShow || (shouldShow && userSettings[generateKey(item)] === undefined)
    }).map(item => [
      generateKey(item),
      item.hide,
    ] as [string, boolean])
  }

  /**
   * 批量隐藏（元素&标签）
   */
  hiddenElement(): void {
    const userSettings = VisibilitySettingsManager.getAll()
    const isEmpty = Object.keys(userSettings).length === 0
    // 内置规则
    const elements = this.webElement.getSelectors() ?? []
    // 收集所有需要监听的选择器
    const watchedSelectors: DynamicHideMetaOptions = []

    if (!isEmpty) {
      const userSettingsConfig = Object.entries(userSettings)

      const mergedSettings = [
        ...userSettingsConfig.map(item => ({
          key: item[0],
          hide: item[1],
          source: ElementConfigSource.Storage,
        })),
        ...this.getNotStorageSelectors(userSettings).map(item => ({
          key: item[0],
          hide: item[1],
          source: ElementConfigSource.System,
        })),
      ]

      const elementOptions = this.CSSHandler.processElementVisibilitySettings(mergedSettings)
      logger.important(`[${this.name}] 已加载 ${elementOptions.length} 条元素过滤规则`)

      // 1. 使用样式注入隐藏元素
      const operatorLog: string[] = []
      const injectOptions = elementOptions.reduce((acc: IElementCSSOption[], options) => {
        const hideResult = checkElementHideSettings(options)
        const { cssOperation, injectorType } = hideResult
        if (cssOperation === ElementHideCssOperation.Inject) {
          const { hostSelector, selector, source } = options
          operatorLog.push(this.recordCssOperatorLog(hostSelector, selector, injectorType, source))

          acc.push({
            ...options,
            injectorType,
            operation: ElementHideCssOperation.Inject,
          })
        }

        return acc
      }, [])

      if (injectOptions.length > 0) {
        logger.important(`[${this.name}] 开始注入隐藏样式，共 ${injectOptions.length} 条规则`)
        operatorLog.forEach(item => logger.added(item))
        this.CSSHandler.addRules(injectOptions)
      }

      // 2. 使用 DOM 隐藏元素
      logger.important(`[${this.name}] 开始 DOM 操作隐藏元素`)
      elementOptions.forEach((options) => {
        const hideResult = checkElementHideSettings(options)
        const { isValid, operationType } = hideResult

        if (!isValid)
          return

        switch (operationType) {
          case ElementHideOperationType.EventDriven: {
            options.handler!()
            break
          }
          case ElementHideOperationType.Dynamic: {
            watchedSelectors.push(options)
            break
          }
          default: {
            // 首次初始化时，不处理显示元素
            if (!(this.isInitialized ?? true) && !(options.hide ?? true))
              return

            ToolKit.ui.element.dealElementVisible(options, false)
          }
        }
      })
    }
    else {
      // 1-1. 使用样式注入隐藏元素（类型一）
      const elementOperatorLog: string[] = []
      const elementInjectOptions = elements.reduce((acc: IElementCSSOption[], element) => {
        const source = ElementConfigSource.System

        const hideResult = checkElementHideSettings(element as ElementHideConfig)
        const { cssOperation, injectorType } = hideResult
        if (cssOperation === ElementHideCssOperation.Inject) {
          const { hostSelector, selector } = element
          elementOperatorLog.push(this.recordCssOperatorLog(hostSelector, selector, injectorType, source))

          acc.push({
            name: element.nameZh!,
            domScope: element.domScope!,
            selectorType: element.selectorType,
            selector: element.selector,
            source,
            injectorType,
            operation: ElementHideCssOperation.Inject,
          })
        }

        return acc
      }, [])

      if (elementInjectOptions.length > 0) {
        logger.important(`[${this.name}] 开始注入隐藏样式（类型一），共 ${elementInjectOptions.length} 条规则`)
        elementOperatorLog.forEach(item => logger.added(item))
        this.CSSHandler.addRules(elementInjectOptions)
      }

      // 1-2. 使用 DOM 隐藏元素（类型一）
      if (elements.length > 0)
        logger.important(`[${this.name}] 开始 DOM 操作隐藏元素（类型一）`)
      elements.forEach((element) => {
        const hideResult = checkElementHideSettings(element as ElementHideConfig)
        const { isValid, operationType } = hideResult

        if (!isValid)
          return

        switch (operationType) {
          case ElementHideOperationType.EventDriven: {
            element.handler!()
            break
          }
          case ElementHideOperationType.Dynamic: {
            watchedSelectors.push({
              type: ELEMENT_SAVE_CONFIG.ELEMENT.id,
              domScope: element.domScope!,
              hostSelector: element.hostSelector!,
              selector: element.selector,
              clearChildStyles: element.clearChildStyles,
              listenOptions: element.listenOptions,
              compoundOptions: element.compoundOptions,
              source: ElementConfigSource.System,
            })
            break
          }
          case ElementHideOperationType.Static: {
            ToolKit.ui.element.hideElement(element.selector, {
              domScope: element.domScope,
              hostSelector: element.hostSelector,
              clearChildStyles: element.clearChildStyles,
              compoundOptions: element.compoundOptions,
            })
            break
          }

          default:
            break
        }
      })

      const elementTags = this.elementTag.getSelectors() ?? []

      // 2-1. 使用样式注入隐藏元素（类型二）
      const tagOperatorLog: string[] = []
      const tagInjectOptions = elementTags.reduce((acc: IElementCSSOption[], elTag) => {
        const source = ElementConfigSource.System
        const selector = ToolKit.ui.element.getTagSelector(elTag.tag!)

        const hideResult = checkElementHideSettings(elTag as unknown as ElementHideConfig)
        const { cssOperation, injectorType } = hideResult
        if (cssOperation === ElementHideCssOperation.Inject) {
          const { hostSelector } = elTag
          this.recordCssOperatorLog(hostSelector, selector, injectorType, source)

          acc.push({
            name: elTag.nameZh!,
            domScope: elTag.domScope!,
            selectorType: elTag.selectorType,
            selector,
            source,
            injectorType,
            operation: ElementHideCssOperation.Inject,
          })
        }

        return acc
      }, [])

      if (tagInjectOptions.length > 0) {
        logger.important(`[${this.name}] 开始注入隐藏样式（类型二），共 ${tagInjectOptions.length} 条规则`)
        tagOperatorLog.forEach(item => logger.added(item))
        this.CSSHandler.addRules(tagInjectOptions)
      }

      // 2-2. 使用 DOM 隐藏元素（类型二）
      if (elementTags.length > 0)
        logger.important(`[${this.name}] 开始 DOM 操作隐藏元素（类型二）`)
      elementTags.forEach((elTag) => {
        const hideResult = checkElementHideSettings(elTag as unknown as ElementHideConfig)
        const { isValid, operationType } = hideResult
        const selector = ToolKit.ui.element.getTagSelector(elTag.tag!)
        const clearChildStyles = elTag.clearChildStyles

        // 初始处理
        const processElement = () => {
          if (!isValid)
            return

          switch (operationType) {
            case ElementHideOperationType.EventDriven: {
              elTag.handler!()
              break
            }
            case ElementHideOperationType.Dynamic: {
              watchedSelectors.push({
                type: ELEMENT_SAVE_CONFIG.TAG.id,
                domScope: elTag.domScope!,
                hostSelector: elTag.hostSelector!,
                selector,
                clearChildStyles,
                listenOptions: elTag.listenOptions,
                compoundOptions: undefined,
                source: ElementConfigSource.System,
              })
              break
            }
            case ElementHideOperationType.Static: {
              ToolKit.ui.element.hideElement(selector, {
                domScope: elTag.domScope,
                hostSelector: elTag.hostSelector,
                clearChildStyles,
              })
              break
            }
            default:
              break
          }

          if (elTag.hideParentSiblings) {
            // 隐藏元素的父级元素的所有同级节点
            $(selector).parent().siblings().hide()
          }
        }

        // 立即执行一次
        processElement()
      })
      logger.debug('内置规则加载完成')
    }

    if (watchedSelectors.length > 0) {
      logger.important(`[${this.name}] 隐藏动态元素，共 ${watchedSelectors.length} 条规则`)
      DynamicHide.getInstance().pollDynamicHideElements(watchedSelectors)
    }
  }

  /**
   * 记录 CSS 注入日志
   */
  recordCssOperatorLog(hostSelector: string | undefined, selector: string, injectorType: InjectorType | undefined, source: ElementConfigSource): string {
    const hostSelectorInfo = hostSelector ? `（${hostSelector}）` : ''
    return `[${this.name}] [${source}] [${injectorType}] ${selector}${hostSelectorInfo}`
  }

  applyConfig(config: Partial<Config>): void {
    super.updateConfig(config)

    this.hiddenElement()

    logger.info(`[${this.name}] Config updated and apply`, this.config)
  }
}
